#!/bin/bash
# =============================================================================
# Backup Restore Script / 备份恢复脚本
# {{ ansible_managed }}
# 
# 功能说明 / Features:
# - 恢复系统配置文件 / Restore system configuration files
# - 恢复应用程序数据 / Restore application data
# - 恢复数据库备份 / Restore database backups
# - 恢复日志文件 / Restore log files
# - 支持指定日期恢复 / Support restore from specific date
# - 自动备份当前文件 / Automatic backup of current files
# - 恢复前完整性验证 / Pre-restore integrity verification
# - 并行恢复支持 / Parallel restore support
# 
# 使用方法 / Usage:
#   ./restore.sh [restore_type] [backup_date]
#   restore_type: config|data|database|logs|all (默认: config)
#   backup_date: YYYYMMDD 或 latest (默认: latest)
# 
# 示例 / Examples:
#   ./restore.sh config latest          # 恢复最新的配置备份
#   ./restore.sh data 20231201          # 恢复指定日期的数据备份
#   ./restore.sh all latest             # 恢复所有组件的最新备份
# =============================================================================

# 启用严格错误处理 / Enable strict error handling
set -euo pipefail

# =============================================================================
# 配置参数 / Configuration Parameters
# =============================================================================

# 恢复类型 - 从命令行参数获取 / Restore type from command line
RESTORE_TYPE="${1:-config}"                                 # 恢复类型（config/data/database/logs/all）

# 备份日期 - 从命令行参数获取 / Backup date from command line
BACKUP_DATE="${2:-latest}"                                  # 备份日期（YYYYMMDD或latest）

# 目录配置 / Directory Configuration
BACKUP_BASE_DIR="{{ backup_base_dir }}"                     # 备份根目录 / Backup base directory
RESTORE_STAGING_DIR="{{ backup_restore_config.staging_dir }}" # 恢复暂存目录 / Restore staging directory
BACKUP_LOG_DIR="{{ backup_log_dir }}"                       # 日志目录 / Log directory

# 时间戳和日志 / Timestamp and Logging
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")                          # 当前时间戳 / Current timestamp
LOG_FILE="${BACKUP_LOG_DIR}/restore_${RESTORE_TYPE}_${TIMESTAMP}.log"  # 恢复日志文件 / Restore log file

# 恢复选项 / Restore Options
BACKUP_ORIGINAL="{{ backup_restore_config.backup_original | lower }}"           # 是否备份原文件 / Whether to backup original files
VERIFY_BEFORE_RESTORE="{{ backup_restore_config.verify_before_restore | lower }}" # 恢复前是否验证 / Whether to verify before restore
PARALLEL_JOBS="{{ backup_restore_config.parallel_jobs }}"   # 并行任务数 / Number of parallel jobs

# Functions
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

error_exit() {
    log "ERROR: $1"
    exit 1
}

confirm_action() {
    local message="$1"
    echo "$message"
    read -p "Do you want to continue? (yes/no): " -r
    if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
        log "Restore operation cancelled by user"
        exit 0
    fi
}

find_latest_backup() {
    local backup_type="$1"
    local component="$2"
    
    local backup_path="${BACKUP_BASE_DIR}/${backup_type}/${component}"
    
    if [[ ! -d "$backup_path" ]]; then
        error_exit "No backups found for $component in $backup_path"
    fi
    
    local latest_backup=$(find "$backup_path" -maxdepth 1 -type d -name "*" | sort -r | head -n 1)
    
    if [[ -z "$latest_backup" ]]; then
        error_exit "No backup directories found in $backup_path"
    fi
    
    echo "$latest_backup"
}

find_backup_by_date() {
    local backup_type="$1"
    local component="$2"
    local date="$3"
    
    local backup_path="${BACKUP_BASE_DIR}/${backup_type}/${component}"
    
    local backup_dir=$(find "$backup_path" -maxdepth 1 -type d -name "*${date}*" | head -n 1)
    
    if [[ -z "$backup_dir" ]]; then
        error_exit "No backup found for date $date in $backup_path"
    fi
    
    echo "$backup_dir"
}

get_backup_directory() {
    local backup_type="$1"
    local component="$2"
    
    if [[ "$BACKUP_DATE" == "latest" ]]; then
        find_latest_backup "$backup_type" "$component"
    else
        find_backup_by_date "$backup_type" "$component" "$BACKUP_DATE"
    fi
}

backup_current_files() {
    local target_path="$1"
    local backup_name="$2"
    
    if [[ "$BACKUP_ORIGINAL" == "true" ]] && [[ -e "$target_path" ]]; then
        log "Backing up current $backup_name to ${target_path}.backup.${TIMESTAMP}"
        
        if [[ -d "$target_path" ]]; then
            cp -r "$target_path" "${target_path}.backup.${TIMESTAMP}"
        else
            cp "$target_path" "${target_path}.backup.${TIMESTAMP}"
        fi
    fi
}

verify_backup_integrity() {
    local backup_dir="$1"
    
    log "Verifying backup integrity in $backup_dir"
    
    # Check if manifest exists
    if [[ -f "${backup_dir}/MANIFEST.txt" ]]; then
        log "Found backup manifest"
    else
        log "WARNING: No backup manifest found"
    fi
    
    # Verify compressed files
    local verification_failed=false
    
    find "$backup_dir" -name "*.tar.gz" | while read -r file; do
        if ! tar -tzf "$file" >/dev/null 2>&1; then
            log "ERROR: Corrupted tar.gz file: $file"
            verification_failed=true
        fi
    done
    
    find "$backup_dir" -name "*.gz" ! -name "*.tar.gz" | while read -r file; do
        if ! gzip -t "$file" 2>/dev/null; then
            log "ERROR: Corrupted gzip file: $file"
            verification_failed=true
        fi
    done
    
    # Verify checksums if available
    find "$backup_dir" -name "*.sha256" | while read -r checksum_file; do
        if ! sha256sum -c "$checksum_file" >/dev/null 2>&1; then
            log "ERROR: Checksum verification failed for $(basename "$checksum_file" .sha256)"
            verification_failed=true
        fi
    done
    
    if [[ "$verification_failed" == "true" ]]; then
        error_exit "Backup integrity verification failed"
    fi
    
    log "Backup integrity verification successful"
}

restore_configs() {
    log "Starting configuration restore..."
    
    local backup_dir
    backup_dir=$(get_backup_directory "daily" "configs")
    
    if [[ "$VERIFY_BEFORE_RESTORE" == "true" ]]; then
        verify_backup_integrity "$backup_dir"
    fi
    
    log "Restoring configurations from $backup_dir"
    
{% for config in backup_configs %}
{% if config.enabled %}
    # Restore {{ config.name }}
    local {{ config.name }}_backup="${backup_dir}/{{ config.name }}_*.tar.gz"
    if ls ${{ config.name }}_backup 1> /dev/null 2>&1; then
        log "Restoring {{ config.name }} configuration"
        
        backup_current_files "{{ config.source }}" "{{ config.name }}"
        
        # Extract to staging directory first
        mkdir -p "${RESTORE_STAGING_DIR}/{{ config.name }}"
        tar -xzf ${{ config.name }}_backup -C "${RESTORE_STAGING_DIR}/{{ config.name }}"
        
        # Move to final location
        if [[ -d "{{ config.source }}" ]]; then
            rm -rf "{{ config.source }}"
        fi
        
        mv "${RESTORE_STAGING_DIR}/{{ config.name }}/$(basename {{ config.source }})" "{{ config.source }}"
        
        log "{{ config.name }} configuration restored successfully"
    else
        log "WARNING: No backup found for {{ config.name }}"
    fi
{% endif %}
{% endfor %}
    
    log "Configuration restore completed"
}

restore_data() {
    log "Starting application data restore..."
    
    local backup_dir
    backup_dir=$(get_backup_directory "daily" "data")
    
    if [[ "$VERIFY_BEFORE_RESTORE" == "true" ]]; then
        verify_backup_integrity "$backup_dir"
    fi
    
    log "Restoring application data from $backup_dir"
    
{% for data in backup_data %}
{% if data.enabled %}
    # Restore {{ data.name }}
    local {{ data.name }}_backup="${backup_dir}/{{ data.name }}_*.tar.gz"
    if ls ${{ data.name }}_backup 1> /dev/null 2>&1; then
        log "Restoring {{ data.name }} data"
        
        backup_current_files "{{ data.source }}" "{{ data.name }}"
        
        # Stop related services before restore
        systemctl stop {{ data.name }} 2>/dev/null || true
        
        # Extract to staging directory first
        mkdir -p "${RESTORE_STAGING_DIR}/{{ data.name }}"
        tar -xzf ${{ data.name }}_backup -C "${RESTORE_STAGING_DIR}/{{ data.name }}"
        
        # Ensure target directory exists
        mkdir -p "{{ data.source }}"
        
        # Move to final location
        cp -r "${RESTORE_STAGING_DIR}/{{ data.name }}"/* "{{ data.source }}/"
        
        # Set proper permissions
        chown -R {{ backup_security_config.owner }}:{{ backup_security_config.group }} "{{ data.source }}"
        
        # Restart related services
        systemctl start {{ data.name }} 2>/dev/null || true
        
        log "{{ data.name }} data restored successfully"
    else
        log "WARNING: No backup found for {{ data.name }}"
    fi
{% endif %}
{% endfor %}
    
    log "Application data restore completed"
}

restore_databases() {
    log "Starting database restore..."
    
    local backup_dir
    backup_dir=$(get_backup_directory "weekly" "databases")
    
    if [[ "$VERIFY_BEFORE_RESTORE" == "true" ]]; then
        verify_backup_integrity "$backup_dir"
    fi
    
    log "Restoring databases from $backup_dir"
    
{% for db in backup_databases %}
{% if db.enabled %}
    # Restore {{ db.name }} ({{ db.type }})
    case "{{ db.type }}" in
        "mysql")
            log "Restoring MySQL databases for {{ db.name }}"
            
{% for database in db.databases %}
            local mysql_backup="${backup_dir}/mysql/{{ db.name }}/{{ database }}_*.sql.gz"
            if ls $mysql_backup 1> /dev/null 2>&1; then
                log "Restoring MySQL database {{ database }}"
                
                # Create database if not exists
                mysql -h {{ db.host }} -P {{ db.port }} -u {{ db.username }} -p{{ db.password }} \
                    -e "CREATE DATABASE IF NOT EXISTS {{ database }};"
                
                # Restore database
                gunzip -c $mysql_backup | mysql -h {{ db.host }} -P {{ db.port }} -u {{ db.username }} -p{{ db.password }} {{ database }}
                
                log "MySQL database {{ database }} restored successfully"
            else
                log "WARNING: No backup found for MySQL database {{ database }}"
            fi
{% endfor %}
            ;;
        "postgresql")
            log "Restoring PostgreSQL databases for {{ db.name }}"
            
            export PGPASSWORD="{{ db.password }}"
            
{% for database in db.databases %}
            local pg_backup="${backup_dir}/postgresql/{{ db.name }}/{{ database }}_*.dump.gz"
            if ls $pg_backup 1> /dev/null 2>&1; then
                log "Restoring PostgreSQL database {{ database }}"
                
                # Drop and recreate database
                dropdb -h {{ db.host }} -p {{ db.port }} -U {{ db.username }} {{ database }} 2>/dev/null || true
                createdb -h {{ db.host }} -p {{ db.port }} -U {{ db.username }} {{ database }}
                
                # Restore database
                gunzip -c $pg_backup | pg_restore -h {{ db.host }} -p {{ db.port }} -U {{ db.username }} -d {{ database }} --no-password
                
                log "PostgreSQL database {{ database }} restored successfully"
            else
                log "WARNING: No backup found for PostgreSQL database {{ database }}"
            fi
{% endfor %}
            
            unset PGPASSWORD
            ;;
    esac
{% endif %}
{% endfor %}
    
    log "Database restore completed"
}

restore_logs() {
    log "Starting log restore..."
    
    local backup_dir
    backup_dir=$(get_backup_directory "daily" "logs")
    
    if [[ "$VERIFY_BEFORE_RESTORE" == "true" ]]; then
        verify_backup_integrity "$backup_dir"
    fi
    
    log "Restoring logs from $backup_dir"
    
{% for log_config in backup_logs %}
{% if log_config.enabled %}
    # Restore {{ log_config.name }} logs
    local {{ log_config.name }}_logs_backup="${backup_dir}/{{ log_config.name }}_logs_*.tar.gz"
    if ls ${{ log_config.name }}_logs_backup 1> /dev/null 2>&1; then
        log "Restoring {{ log_config.name }} logs"
        
        backup_current_files "{{ log_config.source }}" "{{ log_config.name }}_logs"
        
        # Extract logs to staging directory
        mkdir -p "${RESTORE_STAGING_DIR}/{{ log_config.name }}_logs"
        tar -xzf ${{ log_config.name }}_logs_backup -C "${RESTORE_STAGING_DIR}/{{ log_config.name }}_logs"
        
        # Move to final location
        mkdir -p "{{ log_config.source }}"
        cp -r "${RESTORE_STAGING_DIR}/{{ log_config.name }}_logs"/* "{{ log_config.source }}/"
        
        log "{{ log_config.name }} logs restored successfully"
    else
        log "WARNING: No backup found for {{ log_config.name }} logs"
    fi
{% endif %}
{% endfor %}
    
    log "Log restore completed"
}

list_available_backups() {
    log "Available backups:"
    
    echo "Configuration Backups:"
    find "${BACKUP_BASE_DIR}" -path "*/configs/*" -type d -name "*" | sort
    
    echo ""
    echo "Data Backups:"
    find "${BACKUP_BASE_DIR}" -path "*/data/*" -type d -name "*" | sort
    
    echo ""
    echo "Database Backups:"
    find "${BACKUP_BASE_DIR}" -path "*/databases/*" -type d -name "*" | sort
    
    echo ""
    echo "Log Backups:"
    find "${BACKUP_BASE_DIR}" -path "*/logs/*" -type d -name "*" | sort
}

cleanup_staging() {
    log "Cleaning up staging directory"
    rm -rf "${RESTORE_STAGING_DIR}"/*
}

# Main execution
main() {
    log "Starting restore process ($RESTORE_TYPE)..."
    
    # Create necessary directories
    mkdir -p "$RESTORE_STAGING_DIR" "$BACKUP_LOG_DIR"
    
    case "$RESTORE_TYPE" in
        "list")
            list_available_backups
            exit 0
            ;;
        "config")
            confirm_action "This will restore configuration files. Current files will be backed up if enabled."
            restore_configs
            ;;
        "data")
            confirm_action "This will restore application data. Services will be stopped during restore."
            restore_data
            ;;
        "database")
            confirm_action "This will restore databases. Existing data will be overwritten."
            restore_databases
            ;;
        "logs")
            confirm_action "This will restore log files."
            restore_logs
            ;;
        "full")
            confirm_action "This will perform a full restore of all components. This is a destructive operation."
            restore_configs
            restore_data
            restore_databases
            restore_logs
            ;;
        *)
            error_exit "Unknown restore type: $RESTORE_TYPE. Available types: config, data, database, logs, full, list"
            ;;
    esac
    
    # Cleanup
    cleanup_staging
    
    log "Restore process completed successfully"
    log "Please verify that all services are running correctly"
}

# Execute main function
main "$@"